﻿using GammaJul.ReSharper.GammaXaml.Highlightings.CSharp;
using GammaJul.ReSharper.GammaXaml.Psi.Xaml;
using JetBrains.Annotations;
using JetBrains.DocumentModel;
using JetBrains.ReSharper.Daemon;
using JetBrains.ReSharper.Daemon.CSharp.Stages;
using JetBrains.ReSharper.Daemon.Stages;
using JetBrains.ReSharper.Daemon.Stages.Dispatcher;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.CSharp.Tree;
using JetBrains.ReSharper.Psi.Xaml.Impl.Resolve;

namespace GammaJul.ReSharper.GammaXaml.Analysis.CSharp {

	/// <summary>
	/// Analyzer that checks that :
	///  - A CLR property having a backing dependency property field has a matching type
	///  - An attached getter/setter method having a backing dependency property field has a matching type
	/// </summary>
	[ElementProblemAnalyzer(
		new[] { typeof(IClassMemberDeclaration) },
		HighlightingTypes = new[] { typeof(MismatchedDpAccessorTypeHighlighting) }
	)]
	public class DpAccessorProblemAnalyzer : ElementProblemAnalyzer<IClassMemberDeclaration> {

		private enum CheckResult {
			NoType,
			CorrectType,
			IncorrectType
		}

		protected override void Run(IClassMemberDeclaration element, ElementProblemAnalyzerData data, IHighlightingConsumer consumer) {
			IXamlTypeCache xamlTypeCache = XamlTypeCache.GetCache(element);
			
			var propertyDeclaration = element as IPropertyDeclaration;
			if (propertyDeclaration != null) {
				CheckPropertyDeclaration(propertyDeclaration, xamlTypeCache, consumer);
				return;
			}

			var methodDeclaration = element as IMethodDeclaration;
			if (methodDeclaration != null)
				CheckMethodDeclaration(methodDeclaration, xamlTypeCache, consumer);
		}

		private static void CheckPropertyDeclaration([NotNull] IPropertyDeclaration propertyDeclaration, [NotNull] IXamlTypeCache xamlTypeCache, [NotNull] IHighlightingConsumer consumer) {
			
			// only analyze the property if it's in a class derived from DependencyObject
			ITypeElement containingType = propertyDeclaration.GetContainingTypeDeclaration().DeclaredElement;
			if (containingType == null || !containingType.IsDescendantOf(xamlTypeCache.DependencyObject.GetTypeElement()))
				return;

			IProperty property = propertyDeclaration.DeclaredElement;
			IField dpField = property.GetDpField(xamlTypeCache);
			if (dpField == null)
				return;

			DocumentRange highlightingRange = propertyDeclaration.TypeUsage.GetHighlightingRange();

			CheckResult result = CheckDpFieldType(property, property.Type, highlightingRange, dpField, xamlTypeCache, consumer);
			if (result != CheckResult.NoType)
				return;

			dpField = property.GetDpKeyField(xamlTypeCache);
			if (dpField != null)
				CheckDpFieldType(property, property.Type, highlightingRange, dpField, xamlTypeCache, consumer);
		}

		private static void CheckMethodDeclaration([NotNull] IMethodDeclaration methodDeclaration, [NotNull] IXamlTypeCache xamlTypeCache, [NotNull] IHighlightingConsumer consumer) {
			IMethod method = methodDeclaration.DeclaredElement;
			IField dpField = method.GetDpField(xamlTypeCache);
			if (dpField == null)
				return;

			DocumentRange highlightingRange;
			IType accessorType;
			if (method.ShortName.IsDpAttachedGetterName()) {
				highlightingRange = methodDeclaration.TypeUsage.GetHighlightingRange();
				accessorType = method.ReturnType;
			}
			else {
				// GetDpField() already validates that we have a valid attached accessor so we're ok accessing ParameterDeclarations[1] here
				ICSharpParameterDeclaration paramDeclaration = methodDeclaration.ParameterDeclarations[1];
				highlightingRange = paramDeclaration.TypeUsage.GetHighlightingRange();
				accessorType = paramDeclaration.Type;
			}

			CheckResult result = CheckDpFieldType(method, accessorType, highlightingRange, dpField, xamlTypeCache, consumer);
			if (result != CheckResult.NoType)
				return;

			dpField = method.GetDpKeyField(xamlTypeCache);
			if (dpField != null)
				CheckDpFieldType(method, accessorType, highlightingRange, dpField, xamlTypeCache, consumer);
		}

		/// <summary>
		/// Checks that a dependency property field and its given accessor have matching types.
		/// </summary>
		/// <param name="accessor">The accessor.</param>
		/// <param name="accessorType">The accessor type.</param>
		/// <param name="highlightingRange">The range where an highlighting will be placed.</param>
		/// <param name="dpField">The dependency property field.</param>
		/// <param name="xamlTypeCache">A <see cref="IXamlTypeCache"/> instance.</param>
		/// <param name="consumer">The highlighting consumer.</param>
		/// <returns>Whether the type was matching.</returns>
		private static CheckResult CheckDpFieldType([NotNull] IDeclaredElement accessor, [NotNull] IType accessorType, DocumentRange highlightingRange, [NotNull] IField dpField, [NotNull] IXamlTypeCache xamlTypeCache, [NotNull] IHighlightingConsumer consumer) {
			IType dpRegisteredType = dpField.GetDpOrDpKeyInitInfo(xamlTypeCache).RegisteredType;
			if (dpRegisteredType == null)
				return CheckResult.NoType;

			if (Equals(dpRegisteredType, accessorType))
				return CheckResult.CorrectType;

			consumer.AddHighlighting(
				new MismatchedDpAccessorTypeHighlighting(dpField, dpRegisteredType, accessor),
				highlightingRange);
			return CheckResult.IncorrectType;
		}

	}

}